<html>
<head>
<style>
    body {
        color: black;
        padding: 0px 0px 0px 0px;
    }

    .hidden {
        visibility: hidden;
    }
</style>
<script>
function print(message, color)
{
    var paragraph = document.createElement("div");
    paragraph.appendChild(document.createTextNode(message));
    paragraph.style.fontFamily = "monospace";
    if (color)
        paragraph.style.color = color;
    document.getElementById("console").appendChild(paragraph);
}

function shouldBe(a, b)
{
    var evalA = eval(a);
    if (evalA == b)
        print("PASS: " + a + " should be " + b + " and is.", "green");
    else
        print("FAIL: " + a + " should be " + b + " but instead is " + evalA + ".", "red");
}

var event;
function parentEventListener(e)
{
    print("DOM EVENT AFTER GARBAGE COLLECTION");
    gc();
    event = e;
    shouldBe("event.myCustomProperty", 1);
    event = null; // clear JS reference
}

function childEventListener(e)
{
    print("DOM EVENT BEFORE GARBAGE COLLECTION");
    e.myCustomProperty = 1;
    event = e;
    shouldBe("event.myCustomProperty", 1);
    event = null; // clear JS reference
}

function testEvents()
{
    var parent = document.createElement("p");
    var child = document.createElement("p");
    parent.appendChild(child);
    document.body.appendChild(parent);

    if (parent.addEventListener) {
        child.addEventListener("click", childEventListener, false);
        parent.addEventListener("click", parentEventListener, false);
    } else {
        child.attachEvent("onclick", childEventListener);
        parent.attachEvent("onclick", parentEventListener);
    }

    if (document.createEvent) {
        var event = document.createEvent("MouseEvents");
        event.initEvent("click", true, true);
        child.dispatchEvent(event);
    } else {
        child.fireEvent("onclick");
    }
}

function testDOMImplementation()
{
  var impl = document.implementation.createHTMLDocument('').implementation;
  gc();
  impl.createHTMLDocument('');  // May crash or throw an exception if we collect parent document of impl.
}

function test()
{
    if (window.testRunner)
        testRunner.dumpAsText();

    print("DOM OBJECTS BEFORE GARBAGE COLLECTION:");
    generateProperties();

    gc();

    print("DOM OBJECTS AFTER GARBAGE COLLECTION:");
    testPropertiesAgain();

    testEvents();
    testDOMImplementation();
}

// By default, we expect that custom properties are not allowed.
// If "allow custom" is specified, then custom properties are allowed and survive garbage collection.
// If "allow custom skip" is specified, this means this property is not guaranteed to survive garbage
// collection, so we don't test it at all, since there is a chance it might due to conservative GC algorithm.
// Any uses of "allow custom skip" represent bugs that need to be fixed.

var objectsToTest = [
    [ "document.implementation", "allow custom" ], // DOMImplementation
    [ "document.fonts", "allow custom" ], // FontFaceSet
    [ "document", "allow custom" ],
    [ "document.body", "allow custom" ],
    [ "document.body.attributes", "allow custom" ], // NamedNodeMap
    [ "document.getElementsByTagName('body')", "allow custom" ], // NodeList
    [ "document.getElementsByTagName('canvas')[0].getContext('2d')", "allow custom" ], // CanvasRenderingContext2D
    [ "document.getElementsByTagName('canvas')[0].getContext('2d').createLinearGradient(0, 0, 0, 0)" ], // CanvasGradient
    [ "document.getElementsByTagName('canvas')[0].getContext('2d').createPattern(document.getElementsByTagName('canvas')[0], 'no-repeat')" ], // CanvasPattern
    [ "document.getElementsByTagName('select')[0].options", "allow custom" ],
    [ "document.body.childNodes", "allow custom" ], // NodeList
    [ "document.body.dataset", "allow custom" ], // DatasetDOMStringMap (DOMStringMap)

    [ "document.body.classList", "allow custom" ], // ClassList (DOMTokenList)
    [ "document.getElementsByTagName('link')[0].relList", "allow custom" ], // ClassList (DOMTokenList)
    [ "document.getElementsByTagName('link')[0].sizes", "allow custom" ], // ClassList (DOMTokenList)
    [ "document.getElementsByTagName('output')[0].htmlFor", "allow custom" ], // ClassList (DOMTokenList)
    [ "document.getElementsByTagName('output')[0].labels", "allow custom" ], // NodeList
    [ "document.getElementsByTagName('iframe')[0].sandbox", "allow custom" ], // ClassList (DOMTokenList)

    [ "document.all", "allow custom" ],
    [ "document.images", "allow custom" ],
    [ "document.embeds", "allow custom" ],
    [ "document.applets", "allow custom" ],
    [ "document.links", "allow custom" ],
    [ "document.forms", "allow custom" ],
    [ "document.anchors", "allow custom" ],
    [ "document.scripts", "allow custom" ],

    [ "document.getElementsByTagName('form')[0].elements", "allow custom" ],
    [ "document.getElementsByTagName('table')[0].rows", "allow custom" ],
    [ "document.getElementsByTagName('table')[0].rows[0].cells", "allow custom" ],
    [ "document.getElementsByTagName('table')[0].tBodies", "allow custom" ],
    [ "document.getElementsByTagName('table')[0].tBodies[0].rows", "allow custom" ],
    [ "document.body.children", "allow custom" ],
    [ "document.getElementsByTagName('map')[0].areas", "allow custom" ],

    [ "document.body.style", "allow custom" ],
    [ "document.styleSheets", "allow custom" ], // StyleSheetList
    [ "document.styleSheets[0]", "allow custom" ],
    [ "document.styleSheets[0].cssRules", "allow custom"],
    [ "document.styleSheets[0].cssRules[0]", "allow custom" ],
    [ "document.getElementsByTagName('style')[0].sheet", "allow custom" ],
    [ "document.getElementsByTagName('svg')[0].firstChild.sheet", "allow custom" ],

    [ "new XPathEvaluator()" ], // XPathEvaluator
    [ "new XPathEvaluator().evaluate('/', document, null, 0, null)" ], // XPathResult
    [ "document.createNSResolver(document)" ], // XPathNSResolver
    [ "document.createExpression('/', document.createNSResolver(document))" ] // XPathExpression

    // should not cache: NodeIterator, NodeFilter, TreeWalker, XMLHttpRequest
    // add to test: DOMRect, MediaList, Counter, Range
];

function generateProperties()
{
    for (var i = 0; i < objectsToTest.length; i++) { // >
        try {
            eval(objectsToTest[i][0] + ".myCustomProperty = 1;");
        } catch(e) {
            print("NOT SUPPORTED: " + objectsToTest[i][0] + "[ " + e.message + " ]");
        }
        var expectedResult = objectsToTest[i][1] ? 1 : undefined;
        try {
            shouldBe(objectsToTest[i][0] + ".myCustomProperty", expectedResult);
        } catch(e) {
        }
    }
}

function testPropertiesAgain()
{
    for (var i = 0; i < objectsToTest.length; i++) { // >
        if (objectsToTest[i][1] === "allow custom skip")
            continue;
        var expectedResult = objectsToTest[i][1] ? 1 : undefined;
        try {
            shouldBe(objectsToTest[i][0] + ".myCustomProperty", expectedResult);
        } catch(e) {
        }
    }
}

</script>
</head>

<body style="color: black" onload="test();">
<p>This page tests whether custom properties on DOM objects persist after garbage collection.</p>
<p>If the test passes, you'll see a series of 'PASS' messages below.</p>
<p>Because neither WinIE nor FF has reasonable or predictable behavior in this scenario, this
   test just documents our behavior to ensure that we don't change it accidentally. It is not
   a prescription for how things should behave.</p>
<hr>

<div id='console'></div>

<div class='hidden'>
    <canvas></canvas>
    <select></select>
    <object name="object"></object>
    <form></form>
    <table><tbody><tr></tr></tbody></table>
    <map></map>
    <link></link>
    <output></output>
    <iframe></iframe>
    <svg><style></style></svg>
</div>

</body>
</html>
